use super::*;

#[derive(Debug, Default, Clone)]
pub struct Glyph {
	pub name: String, // The name of the glyph. It must be at least one character long. There is no maximum name length. Names must not contain control characters. Different font specifications, such as OpenType, often have their own glyph name restrictions. Authoring tools should not make assumptions about the validity of a glyph’s name for a particular font specification.
	pub format: u64,  // The major format version. 2 for this version.
	pub format_minor: u64, // The minor format version. Optional if the minor version is 0, must be present if the minor version is not 0.
	// advance: Horizontal and vertical metrics.
	pub width: IntegerOrFloat,  // The advance width. Optional if height is defined.	0
	pub height: IntegerOrFloat, // The vertical advance. Optional if width is defined.	0
	// unicode: Unicode code point.
	pub unicodes: Vec<String>,
	// image: An image reference
	pub image: Image,
	// guideline: A reference guideline
	pub guidelines: Vec<GuideLine>,
	// anchor: A reference position.
	pub anchors: Vec<Anchor>,
	// outline: Outline description.
	pub outline: Outline,
	// lib: Custom data storage.
	pub lib: Value,
}
impl Glyph {
	pub fn parse(r: &mut xml::Parser<'_>) -> Result<Self, Error> {
		let mut this = Glyph::default();
		while let Some(evt) = r.next() {
			match evt {
				xml::Event::Start("glyph") => this.read_glyph(r)?,
				xml::Event::Start(_) => return Err(Error::Msg("only one glyph node")),
				_ => {}
			}
		}
		Ok(this)
	}
	fn read_glyph(&mut self, r: &mut xml::Parser<'_>) -> Result<(), Error> {
		loop {
			match r.next().ok_or(Error::Eof)? {
				xml::Event::End("glyph") => break,
				xml::Event::Attr(k, v) => match k {
					"name" => self.name = v.to_string(),
					"format" => self.format = v.parse().map_err(Error::Integer)?,
					"formatMinor" => self.format_minor = v.parse().map_err(Error::Integer)?,
					_ => {}
				},
				xml::Event::Start("advance") => self.read_advance(r)?,
				xml::Event::Start("unicode") => self.read_unicode(r)?,
				xml::Event::Start("image") => self.image = Image::parse(r)?,
				xml::Event::Start("guideline") => self.guidelines.push(GuideLine::parse(r)?),
				xml::Event::Start("anchor") => self.anchors.push(Anchor::parse(r)?),
				xml::Event::Start("outline") => self.outline = Outline::parse(r)?,
				xml::Event::Start("lib") => {
					let mut plist = plist::Parser::new(r).peekable();
					self.lib = Value::from_plist(&mut Parser(&mut plist))?
				}
				xml::Event::Start(tag) => skip_tag(tag, r)?,
				_ => {} // comment
			}
		}
		Ok(())
	}
	fn read_advance(&mut self, r: &mut xml::Parser<'_>) -> Result<(), Error> {
		loop {
			match r.next().ok_or(Error::Eof)? {
				xml::Event::End("advance") => break,
				xml::Event::Attr(k, v) => match k {
					"width" => self.width = v.parse()?,
					"height" => self.height = v.parse()?,
					_ => {}
				},
				xml::Event::Start(tag) => skip_tag(tag, r)?,
				_ => {} // comment
			}
		}
		Ok(())
	}
	fn read_unicode(&mut self, r: &mut xml::Parser<'_>) -> Result<(), Error> {
		loop {
			match r.next().ok_or(Error::Eof)? {
				xml::Event::End("unicode") => break,
				xml::Event::Attr(k, v) => match k {
					"hex" => self.unicodes.push(v.to_string()),
					_ => {}
				},
				xml::Event::Start(tag) => skip_tag(tag, r)?,
				_ => {} // comment
			}
		}
		Ok(())
	}
}

// xScale, xyScale, yxScale, yScale, xOffset, yOffset taken together in that order form an Affine transformation matrix, to be used to transform the image. The default matrix is [1 0 0 1 0 0], the identity transformation.
// One image width unit equals one horizontal font unit and one image height unit equals one vertical font unit before xScale, xyScale, yxScale, yScale, xOffset, yOffset are applied.
#[derive(Debug, Default, Clone)]
pub struct Image {
	pub file_name: String, // The image file name, including any file extension, not an absolute or relative path in the file system.	None
	pub x_scale: IntegerOrFloat, // See below.	1
	pub xy_scale: IntegerOrFloat, // See below.	0
	pub yx_scale: IntegerOrFloat, // See below.	0
	pub y_scale: IntegerOrFloat, // 	See below.	1
	pub x_offset: IntegerOrFloat, // See below.	0
	pub y_offset: IntegerOrFloat, // See below.	0
	pub color: String, // The color that should be applied to the image. The format follows the color definition standard. This attribute is optional.	no color
}
impl Image {
	pub fn parse(r: &mut xml::Parser<'_>) -> Result<Self, Error> {
		let mut this = Self::default();
		loop {
			match r.next().ok_or(Error::Eof)? {
				xml::Event::End("image") => break,
				xml::Event::Attr(k, v) => match k {
					"fileName" => this.file_name = v.to_string(),
					"xScale" => this.x_scale = v.parse()?,
					"xyScale" => this.xy_scale = v.parse()?,
					"yxScale" => this.yx_scale = v.parse()?,
					"yScale" => this.y_scale = v.parse()?,
					"xOffset" => this.x_offset = v.parse()?,
					"yOffset" => this.y_offset = v.parse()?,
					"color" => this.color = v.to_string(),
					_ => {}
				},
				xml::Event::Start(tag) => skip_tag(tag, r)?,
				_ => {} // comment
			}
		}
		Ok(this)
	}
}

// The guideline extends along angle to infinity in both directions out of the point defined by x and y. If y and angle are omitted, the element represents a vertical guideline. If x and angle are omitted, the element represents a horizontal guideline.
#[derive(Debug, Default, Clone)]
pub struct GuideLine {
	pub x: IntegerOrFloat, // The ‘x’ coordinate. Optional if y is provided and angle is not provided. See below for details.	None
	pub y: IntegerOrFloat, // The ‘y’ coordinate. Optional if x is provided and angle is not provided. See below for details.	None
	pub angle: IntegerOrFloat, // The angle of the guideline. This must be an angle between 0 and 360 degrees in a counter-clockwise direction from the horizontal. If x or y are not specified, angle must not be specified. See below for details.	None
	pub name: String, // An arbitrary name for the guideline. This attribute is optional. It must be at least one character long. There is no maximum name length. A name must not contain control characters.	no name
	pub color: String, // The color that should be applied to the guideline. The format follows the color definition standard. This attribute is optional.	no color
	pub identifier: String, // Unique identifier for the guideline. This attribute is not required and should only be added to guidelines as needed. However, once an identifier has been assigned to a guideline it must not be unnecessarily removed or changed. Identifiers may be changed in incoming guidelines during editing operations such as “paste,” but they should be maintained unless a duplicate identifier will be created. The identifier value must be unique within all identifiers (including identifiers for elements other than guidelines) in the glyph that the guideline belongs to but it is not required to be unique among the identifiers assigned in other glyphs or in fontinfo.plist. The identifier specification is detailed in the conventions.	no identifier
}
impl GuideLine {
	pub fn parse(r: &mut xml::Parser<'_>) -> Result<Self, Error> {
		let mut this = Self::default();
		loop {
			match r.next().ok_or(Error::Eof)? {
				xml::Event::End("guideline") => break,
				xml::Event::Attr(k, v) => match k {
					"x" => this.x = v.parse()?,
					"y" => this.y = v.parse()?,
					"angle" => this.angle = v.parse()?,
					"name" => this.name = v.to_string(),
					"color" => this.color = v.to_string(),
					"identifier" => this.identifier = v.to_string(),
					_ => {}
				},
				xml::Event::Start(tag) => skip_tag(tag, r)?,
				_ => {} // comment
			}
		}
		Ok(this)
	}
}

#[derive(Debug, Default, Clone)]
pub struct Anchor {
	pub x: IntegerOrFloat,  // The ‘x’ coordinate.	None
	pub y: IntegerOrFloat,  // The ‘y’ coordinate.	None
	pub name: String, // An arbitrary name for the anchor. This attribute is optional. It must be at least one character long. There is no maximum name length. A name must not contain control characters.	no name
	pub color: String, // The color that should be applied to the anchor. The format follows the color definition standard. This attribute is optional.	no color
	pub identifier: String, // Unique identifier for the anchor. This attribute is not required and should only be added to anchors as needed. However, once an identifier has been assigned to an anchor it must not be unnecessarily removed or changed. Identifiers may be changed in incoming anchors during editing operations such as “paste,” but they should be maintained unless a duplicate identifier will be created. The identifier value must be unique within all identifiers (including identifiers for elements other than anchors) in the glyph that the anchor belongs to but it is not required to be unique among the identifiers assigned in other glyphs or in fontinfo.plist. The identifier specification is detailed in the conventions.	no identifier
}
impl Anchor {
	pub fn parse(r: &mut xml::Parser<'_>) -> Result<Self, Error> {
		let mut this = Self::default();
		loop {
			match r.next().ok_or(Error::Eof)? {
				xml::Event::End("anchor") => break,
				xml::Event::Attr(k, v) => match k {
					"x" => this.x = v.parse()?,
					"y" => this.y = v.parse()?,
					"name" => this.name = v.to_string(),
					"color" => this.color = v.to_string(),
					"identifier" => this.identifier = v.to_string(),
					_ => {}
				},
				xml::Event::Start(tag) => skip_tag(tag, r)?,
				_ => {} // comment
			}
		}
		Ok(this)
	}
}

#[derive(Debug, Default, Clone)]
pub struct Outline {
	pub comps: Vec<Component>,
	pub conts: Vec<Contour>,
}
impl Outline {
	pub fn parse(r: &mut xml::Parser<'_>) -> Result<Self, Error> {
		let mut this = Self::default();
		loop {
			match r.next().ok_or(Error::Eof)? {
				xml::Event::End("outline") => break,
				xml::Event::Start("component") => this.comps.push(Component::parse(r)?),
				xml::Event::Start("contour") => this.conts.push(Contour::parse(r)?),
				xml::Event::Start(tag) => skip_tag(tag, r)?,
				_ => {} // comment
			}
		}
		Ok(this)
	}
}

#[derive(Debug, Default, Clone)]
pub struct Component {
	pub base: String,             // Name of the base glyph	None
	pub x_scale: IntegerOrFloat,  // See below.	1
	pub xy_scale: IntegerOrFloat, // See below.	0
	pub yx_scale: IntegerOrFloat, // See below.	0
	pub y_scale: IntegerOrFloat,  // See below.	1
	pub x_offset: IntegerOrFloat, // See below.	0
	pub y_offset: IntegerOrFloat, // See below.	0
	pub identifier: String, // Unique identifier for the component. This attribute is not required and should only be added to components as needed. However, once an identifier has been assigned to a component it must not be unnecessarily removed or changed. Identifiers may be changed in incoming components during editing operations such as “paste,” but they should be maintained unless a duplicate identifier will be created. The identifier value must be unique within all identifiers (including identifiers for elements other than components) in the glyph that the component belongs to but it is not required to be unique among the identifiers assigned in other glyphs, in any layerinfo.plist or in fontinfo.plist. The identifier specification is detailed in the conventions.	no identifier
}
impl Component {
	pub fn parse(r: &mut xml::Parser<'_>) -> Result<Self, Error> {
		let mut this = Self::default();
		loop {
			match r.next().ok_or(Error::Eof)? {
				xml::Event::End("component") => break,
				xml::Event::Attr(k, v) => match k {
					"base" => this.base = v.to_string(),
					"xScale" => this.x_scale = v.parse()?,
					"xyScale" => this.xy_scale = v.parse()?,
					"yxScale" => this.yx_scale = v.parse()?,
					"yScale" => this.y_scale = v.parse()?,
					"xOffset" => this.x_offset = v.parse()?,
					"yOffset" => this.y_offset = v.parse()?,
					"identifier" => this.identifier = v.to_string(),
					_ => {}
				},
				xml::Event::Start(tag) => skip_tag(tag, r)?,
				_ => {} // comment
			}
		}
		Ok(this)
	}
}

#[derive(Debug, Default, Clone)]
pub struct Contour {
	pub identifier: String,
	pub points: Vec<Point>,
}
impl Contour {
	pub fn parse(r: &mut xml::Parser<'_>) -> Result<Self, Error> {
		let mut this = Self::default();
		loop {
			match r.next().ok_or(Error::Eof)? {
				xml::Event::End("contour") => break,
				xml::Event::Attr(k, v) => match k {
					"identifier" => this.identifier = v.to_string(),
					_ => {}
				},
				xml::Event::Start("point") => this.points.push(Point::parse(r)?),
				xml::Event::Start(tag) => skip_tag(tag, r)?,
				_ => {} // comment
			}
		}
		Ok(this)
	}
}

#[derive(Debug, Default, Clone)]
pub struct Point {
	pub x: IntegerOrFloat,  // The ‘x’ coordinate.	None
	pub y: IntegerOrFloat,  // The ‘y’ coordinate.	None
	pub cmd: String,        // The point and/or segment type. The options are detailed below.	offcurve
	pub smooth: String, // This attribute must only be given when type indicates the point is on-curve. When set to yes, it signifies that a smooth curvature should be maintained at this point, either as a curve point or a tangent point in Fontographer terms. This attribute may be set for all point types except offcurve.	no
	pub name: String, // Arbitrary name or label for this point. The name does not have to be unique within a contour, nor within an outline. It must be at least one character long. There is no maximum name length. A name must not contain control characters.	no name
	pub identifier: String, // Unique identifier for the point. This attribute is not required and should only be added to points as needed. However, once an identifier has been assigned to a point it must not be unnecessarily removed or changed. Identifiers may be changed in incoming points during editing operations such as “paste” and component decomposition, but they should be maintained unless a duplicate identifier will be created. Identifiers should also be retained when possible during outline manipulation operations such as path direction changes and remove overlap. The identifier value must be unique within all identifiers (including identifiers for elements other than poinys) in the glyph that the point belongs to but it is not required to be unique among the identifiers assigned in other glyphs, in any layerinfo.plist or in fontinfo.plist. The identifier specification is detailed in the conventions.	no identifier
}
impl Point {
	pub fn parse(r: &mut xml::Parser<'_>) -> Result<Self, Error> {
		let mut this = Self::default();
		loop {
			match r.next().ok_or(Error::Eof)? {
				xml::Event::End("point") => break,
				xml::Event::Attr(k, v) => match k {
					"x" => this.x = v.parse()?,
					"y" => this.y = v.parse()?,
					"type" => this.cmd = v.to_string(),
					"smooth" => this.smooth = v.to_string(),
					"name" => this.name = v.to_string(),
					"identifier" => this.identifier = v.to_string(),
					_ => {}
				},
				xml::Event::Start(tag) => skip_tag(tag, r)?,
				_ => {} // comment
			}
		}
		Ok(this)
	}
}

fn skip_tag(tag: &str, r: &mut xml::Parser<'_>) -> Result<(), Error> {
	println!("skip:{}", tag);
	loop {
		match r.next().ok_or(Error::Eof)? {
			xml::Event::End(end) if end == tag => break,
			_ => {}
		}
	}
	Ok(())
}

#[cfg(test)]
mod tests {
	use super::*;
	#[test]
	fn fontinfotest() {
		let content = std::fs::read_to_string("assets/ufo/fontinfotest.ufo/glyphs/a.glif").unwrap();
		let g = Glyph::parse(&mut xml::Parser::new(&content)).unwrap();
		println!("{:#?}", g);
	}
	#[test]
	fn identifiers() {
		let content = std::fs::read_to_string("assets/ufo/identifiers.ufo/glyphs/test.glif").unwrap();
		let g = Glyph::parse(&mut xml::Parser::new(&content)).unwrap();
		println!("{:#?}", g);
	}
}
